/////////////////////////////////////////////////////////////////////////////
// Purpose:     Remote connections through sockets management - generic
// Author:      Jesus Gonzalez
// Modified by:
// Copyright:   (c) 2003-2013 Jesus Gonzalez <jgonzalez@gdr-sistemas.com>
// License:     BSD License
/////////////////////////////////////////////////////////////////////////////

#include "SmartLib/Socket.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

#include "Log.h"

#ifndef NET_SOCKET_RXBUF_SIZE
#define NET_SOCKET_RXBUF_SIZE	1024
#endif

Socket::Socket() : m_fd(-1)
{
}

Socket::Socket( int fd ) : m_fd(fd)
{
}

Socket::~Socket()
{
	Close();
}

void Socket::Close()
{
	if( m_fd >= 0 )
	{
		close( m_fd );
	    LOG_DEBUG( "Socket closed (fd = %d)", m_fd );
		m_fd = -1;
	}
}

bool RemoteSocket::WaitForReception( int timeout )
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't wait for read from a closed socket" );
	}

	struct timeval* ts_timeout = NULL;
	struct timeval _ts_timeout;
	if( timeout >= 0 )
	{
		_ts_timeout.tv_sec = (timeout / 1000);
		_ts_timeout.tv_usec = (timeout % 1000)*1000;
		ts_timeout = &_ts_timeout;
	}

	fd_set fds;
	FD_ZERO( &fds );
	FD_SET( m_fd, &fds );

	// Check if we've got a data ready
	int n = 0;
    while(true)
	{
		n = select( m_fd+1, &fds, NULL, NULL, ts_timeout );
		if( n < 0 )
		{
			if( errno == EINTR ) continue;

			THROW_ERROR( "Error waiting for data (Error=%s)", strerror(errno) );
		}

		break;
	}

    return (n > 0);
}

void RemoteSocket::Receive( ByteArray& buffer, uint size )
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't read from a closed socket" );
	}

	buffer.clear();

	char inbuf[NET_SOCKET_RXBUF_SIZE];

    while( size > 0 )
    {
        size_t max_read;
        if( size > sizeof(inbuf) )
        {
            max_read = sizeof(inbuf);
        }
        else
        {
            max_read = size;
        }

		ssize_t n = recv( m_fd, inbuf, max_read, 0 );
		if( n < 0 )
		{
			if( errno == EINTR ) continue;

			THROW_ERROR( "Failed to read data from the socket (Error=%s)", strerror(errno) );
		}
		else if( n == 0 )
		{
		    // The other side has closed the socket
		    Close();
		}

		buffer.append( inbuf, n );

        size -= n;

        if( (size_t)n < max_read ) break;
	}
}

ByteArray RemoteSocket::Receive(uint size)
{
	ByteArray ret;
	Receive( ret, size );
	return ret;
}

void RemoteSocket::Send( const ByteArray &buffer )
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't write to a closed socket" );
	}

    while(true)
	{
		ssize_t n = send( m_fd, buffer.c_str(), buffer.size(), 0 );
		if( n < 0 )
		{
			if( errno == EINTR ) continue;

			THROW_ERROR( "Failed to write data to the socket (Error=%s)", strerror(errno) );
		}

		if( n != (ssize_t)buffer.size() )
		{
			THROW_ERROR( "Failed to send all data to the host" );
		}

		break;
	}
}

void RemoteSocket::GetInfo( std::string &address, uint16 &port )
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't wait for read from a closed socket" );
	}

	struct sockaddr_in addr;
	socklen_t addr_sz = sizeof(addr);

	if( getpeername( m_fd, (struct sockaddr*) &addr, &addr_sz ) )
	{
		THROW_ERROR( "Can't get remote address (Error=%s)", strerror(errno) );
	}

	address =  inet_ntoa( addr.sin_addr );
	port = ntohs( addr.sin_port );
}

std::string RemoteSocket::GetAddress()
{
	std::string address;
	uint16 port;

	GetInfo( address, port );
	return address;
}

uint16 RemoteSocket::GetPort()
{
	std::string address;
	uint16 port;

	GetInfo( address, port );
	return port;
}
